

// ====================================================================================
// OBJECTS
// ====================================================================================

//Stores inds for a job
function JobIndsData() {
    this.inds = [];
    
    // Method to add an ind (will be overwritten if it exists already)
    this.update = update;
    function update(new_inds) {
        LOG("    JobEvalsData.update");
        for (var i=0; i<new_inds.length; i++) {
            var new_ind = new_inds[i];
            var id = new_ind.__id__;
            if (id < this.inds.length) {
                this.inds[id]=new_ind;
            } else {
                for (var i=this.inds.length; i<id; i++) {
                    this.inds.push({}); //this will be replaced later
                }
                this.inds.push(new_ind);
            }
        }
    }
    
    // Method to get an array of all inds
    this.get_inds_array = get_inds_array;
    function get_inds_array() {
        LOG("    JobEvalsData.get_inds_array");
        return this.inds;
    }
    
    // Method to get the number of inds
    this.num_inds = num_inds;
    function num_inds() {
        LOG("    JobEvalsData.num_inds");
        return this.inds.length;
    }
    
    // Method to get a str representation
    this.str = str;
    function str() {
        LOG("    JobEvalsData.str");
        return "    Num inds = " + this.inds.length;
    }
}

// Stores evals for a job
function JobEvalsData() {
    this.evals = {};
    this.eval_names = [];
    
    // Method to initialise the evaluations
    this._initialise = _initialise;
    function _initialise(first_inds) {
        LOG("    JobEvalsData._initialise");
        for (var i = 0; i < first_inds.length; i++) {
            for (property in first_inds[i]) {
                if (property.charAt(0) != "_") {
                    if (! this.evals.hasOwnProperty(property)) {
                        this.evals[property] = {min:null, max:null};
                        this.eval_names.push(property);
                    }
                }
            }
        }
    }
    
    // Method to update evals from new inds 
    this.update = update;
    function update(new_inds) {
        LOG("    JobEvalsData.update");
        if (this.eval_names.length == 0) {
            this._initialise(new_inds); //this is only done the first time
        }
        for (eval_name in this.evals) {
            var values = new_inds.map(function(new_ind){return new_ind[eval_name];});
            values = values.concat([this.evals[eval_name].min, this.evals[eval_name].max]);
            this.evals[eval_name] = {min:d3.min(values), max:d3.max(values)};
        }
    }
    
    // Method to get domain of eval
    this.get_domain = get_domain;
    function get_domain(eval_name) {
        LOG("    JobEvalsData.get_domain");
        var domain;
        if (! this.evals.hasOwnProperty(eval_name)) {
            domain = [0,1];
        } else {
            domain = [this.evals[eval_name].min, this.evals[eval_name].max];
        }
        return domain;
    }
    
    // Method to get an array of all evals
    this.get_evals_array = get_evals_array;
    function get_evals_array() {
        LOG("    JobEvalsData.get_evals_array");
        return this.evals_array;
    }
    
    // Method to get number of evals
    this.num_evals = num_evals;
    function num_evals() {
        LOG("    JobEvalsData.num_evals");
        return this.eval_names.length;
    }
    
    // Method to get a str
    this.str = str;
    function str() {
        LOG("    JobEvalsData.str");
        var result = "    Evals = " + this.eval_names.join();
        for (eval_name in this.evals) {
            result = result + "\n    " + eval_name + " : " + this.evals[eval_name].min + ", " + this.evals[eval_name].max;
        }
        return result;
    }
}

// ====================================================================================
// FUNCTIONS
// ====================================================================================


// Update the dropdown select menus for the axes
function update_dropdown_options() {
    LOG("update_dropdown_options");
    
    //get the option lists
    var x_axis_name_dropdown = document.getElementById('x_axis_name_dropdown');
    var y_axis_name_dropdown = document.getElementById('y_axis_name_dropdown');

    //check if there are new evaluations
    if (x_axis_name_dropdown.options.length == cur_job_evals.num_evals()) {return;};

    //create options for the drop down
    for (var i=0;i<cur_job_evals.num_evals();i++) {
        x_axis_name_dropdown.options[i] = new Option(cur_job_evals.eval_names[i], i);
        y_axis_name_dropdown.options[i] = new Option(cur_job_evals.eval_names[i], i);
    }
    //make default selections
    x_axis_name_dropdown.selectedIndex = 0;
    y_axis_name_dropdown.selectedIndex = 1; 
    x_axis_name = x_axis_name_dropdown.options[0].text;
    y_axis_name = y_axis_name_dropdown.options[1].text;
    
    LOG("    x_axis_name = " + x_axis_name + ", y_axis_name = " + y_axis_name);
}

// Update the scales
function update_scales() {
    LOG("update_scales");

    // Get the domains
    var x_domain = cur_job_evals.get_domain(x_axis_name);
    var y_domain = cur_job_evals.get_domain(y_axis_name);
    // The x and y scale functions
    x_scale = d3.scale.linear().domain(x_domain).range(x_range());
    y_scale = d3.scale.linear().domain(y_domain).range(y_range());
    
    LOG("    x domain = " + x_domain + "  >>  x range = " + x_range());
    LOG("    y domain = " + y_domain + "  >>  y range = " + y_range());
}

// Draw the svg container and box
function draw_background() {
    LOG("draw_background");
    
    // Resize the svg
    svg_container.attr("width", svg_width()).attr("height", svg_height()).style("background", "#eeeeee");
    
    // Resize the box
    rect.
      attr("x", 0). //xScale(0)).
      attr("y", 0). //yScale(10)).
      attr("width", svg_width()). //xScale(10)).
      attr("height", svg_height()). //yScale(0) ).
      attr("fill", "white").
      attr("fill-opacity", 1).
      attr("stroke", "black").
      attr("stroke-width", 1);
    
    //svg.append("svg:line").
    //  attr("x1", 10).
    //  attr("y1", 10).
    //  attr("x2", $svg_width()).
    //  attr("y2", $svg_height());
    

}

// Delete all the points (circles and labels)
function delete_points() {
    LOG("delete_points");
    LOG(cur_job_inds.str());
    
    // Get the data
    data_array = cur_job_inds.get_inds_array();
    var d3_circles = svg_container.selectAll("circle.point").data(data_array);
    var d3_labels = svg_container.selectAll("text.point").data(data_array);
    
    // I job changed, remove everything
    d3_circles.remove();
    d3_labels.remove();
    d3_circles.exit().remove();
    d3_labels.exit().remove();

}

// Refresh the graphics
function draw_graph() {
    LOG("draw_graph");
    LOG(cur_job_inds.str());
    
    // Get the data
    data_array = cur_job_inds.get_inds_array();
    var d3_circles = svg_container.selectAll("circle.point").data(data_array);
    var d3_labels = svg_container.selectAll("text.point").data(data_array);

    // UPDATE circles selection
    d3_circles.transition().duration(1000).
        attr("class", function(ind) {if (! ind.hasOwnProperty(x_axis_name) || ! ind.hasOwnProperty(y_axis_name)){return "point hide";} else {return "point show";}}).
        attr("cx",function(ind) {return x_scale(ind[x_axis_name]);}).
        attr("cy",function(ind) {return y_scale(ind[y_axis_name]);}).
        attr("r", function(ind) {if (ind._inact_time == null) { return 3;} else {return 2;}}).
        attr("fill", function(ind) {if (ind._inact_time == null) { return "blue";} else {return "black";}}); 
    // ENTER circles selection
    d3_circles.enter().append("circle").
        attr("class", function(ind) {if (! ind.hasOwnProperty(x_axis_name) || ! ind.hasOwnProperty(y_axis_name)){return "point hide";} else {return "point show";}}).
        attr("cx",function(ind) {return x_scale(ind[x_axis_name]);}).
        attr("cy",function(ind) {return y_scale(ind[y_axis_name]);}).
        attr("r", function(ind) {if (ind._inact_time == null) { return 3;} else {return 2;}}).
        attr("fill", function(ind) {if (ind._inact_time == null) { return "blue";} else {return "black";}}); 
    
    
    if (display_labels) {
        // UPDATE labels selection
        d3_labels.transition().
            attr("class", function(ind) {if (! ind.hasOwnProperty(x_axis_name) || ! ind.hasOwnProperty(y_axis_name)){return "point hide";} else {return "point show";}}).                    
            attr("x",function(ind) {return x_scale(ind[x_axis_name]) + label_offset;}).
            attr("y",function(ind) {return y_scale(ind[y_axis_name]) - label_offset;}).
            attr("fill", "black"); 
        // ENTER labels selection
        d3_labels.enter().append("text").
            attr("class", function(ind) {if (! ind.hasOwnProperty(x_axis_name) || ! ind.hasOwnProperty(y_axis_name)){return "point hide";} else {return "point show";}}).
            attr("x",function(ind) {return x_scale(ind[x_axis_name]) + label_offset;}).
            attr("y",function(ind) {return y_scale(ind[y_axis_name]) - label_offset;}).
            attr("fill", "black").
            text(function(ind) {return ind.__id__;});
    } else {
        d3_labels.remove();
        d3_labels.exit().remove();
    }
    
    // Update the axes
    x_axis_s.scale(x_scale).orient("bottom");
    y_axis_s.scale(y_scale).orient("left");
    x_axis_g.attr("class", "axis").attr("transform", "translate( 0 ," + (svg_height() - svg_margin_bottom) +")").call(x_axis_s);
    y_axis_g.attr("class", "axis").attr("transform", "translate(" + svg_margin_left + ", 0 )").call(y_axis_s);
    x_axis_l.attr("class", "axis_label").attr("text-anchor", "middle").attr("x", svg_width()/2 ).attr("y", svg_height() -10 ).text(x_axis_name);
    y_axis_l.attr("class", "axis_label").attr("transform", "rotate(-90)").attr("x", 0 - (svg_height()/2) ).attr("y", 10).attr("dy", "1em").attr("text-anchor", "middle").text(y_axis_name);
}

// Width and height
function svg_width() {return $(window).width() - 20;}
function svg_height() {return $(window).height() - 90;}

// Ranges
function x_range() {return [svg_margin_left, svg_width() - svg_margin_right];}
function y_range() {return [svg_height() - svg_margin_bottom, svg_margin_top];}

// ====================================================================================
// EVENTS
// ====================================================================================

// DEXEN EVENT: Get the updated inds
function on_inds_updated(job_info, new_inds) {
LOG("==================================================");
    LOG("on_inds_updated...");
    
    // Check that we have some inds
    if (!  new_inds) {LOG("No new_inds found..."); return;}
    
    // Update the jobs global variable
    cur_job_inds.update(new_inds);
    
    //Create the dropdown menus
    cur_job_evals.update(new_inds);
    
    // Update dropdowns
    update_dropdown_options();
    
    // Update the scales
    update_scales();
    
    //Do a refresh
    draw_graph();
    
    LOG("All jobs:");
    for (job_id in job_inds) {
        LOG("    " + job_inds[job_id].str())
    }
    
}

//  DEXEN EVENT: Job changed
function cur_job_changed(job_info) {
    LOG("==================================================");
    LOG("cur_job_changed...");
    
    // Delet all existing points (circles and labels)
    delete_points();
    
    //Check we have a job
    if (job_info == null) {
        job_id = -1;
        $("#job_id").text("(No job selected.)");
    } else {
        job_id = job_info.job_id;
        $("#job_id").text("Job ID: " + job_id);
    }
    
    // Create new objects for new job
    if (! job_id in job_inds || ! job_inds[job_id]) {
        LOG("    CREATING NEW JOB UBDS OBJ");
        job_inds[job_id] = new JobIndsData();
    }
    if (! job_id in job_evals || ! job_evals[job_id]) {
        job_evals[job_id] = new JobEvalsData();
    } 
    
    // Set up for this job
    cur_job_inds = job_inds[job_id];
    cur_job_evals = job_evals[job_id];
    
    // Update
    update_scales();
    draw_background();
    draw_graph();
    
    LOG("    New Job ID is " + job_id);
}

// UI EVENT: Dropdown selection event
function x_axis_dropdown_event() {
    LOG("x_axis_dropdown_event");
    var x_axis_name_dropdown=document.getElementById("x_axis_name_dropdown");
    x_axis_name = x_axis_name_dropdown.options[x_axis_name_dropdown.selectedIndex].text;
    //Do a refresh
    update_scales();
    draw_graph();
}

// UI EVENT: Dropdown selection event
function y_axis_dropdown_event() {
    LOG("y_axis_dropdown_event");
    // Set the y axis
    var y_axis_name_dropdown=document.getElementById("y_axis_name_dropdown");
    y_axis_name = y_axis_name_dropdown.options[y_axis_name_dropdown.selectedIndex].text;
    //Do a refresh
    update_scales();
    draw_graph();
}



// WINDOW EVENT: Window resize
$(window).resize(function(){
    LOG("Window has been resized...");

    // Resize the svg element
    d3.select("svg").attr("width", svg_width()).attr("height", svg_height());
    
    //Refresh
    update_scales();
    draw_background();
    draw_graph();
});


// Debugging
function LOG(msg) {
    if (debug) {console.log(msg);}
}

// ====================================================================================
// SCRIPT
// ====================================================================================

// Set debug state to true to print messages
var debug = false;

//The axes
var x_axis_name = "evalA";
var y_axis_name = "evalB";

// All the inds in all jobs
// The data is stored in objects
var job_inds = {};
var job_evals = {};

//Other data
var job_id = -1;
var cur_job_inds;
var cur_job_evals;
var x_scale;
var y_scale;
var job_just_changed = false;
var display_labels = false;

// SVG settings
var svg_margin_bottom = 50;
var svg_margin_left = 70;
var svg_margin_right = 40;
var svg_margin_top = 40;
var label_offset=3;


$(document).ready(function() {
    LOG("Starting script...");

    job_inds[-1] = new JobIndsData();
    job_evals[-1] = new JobEvalsData();

    //Other data
    cur_job_inds = job_inds[-1];
    cur_job_evals = job_evals[-1];
    
    // Create SVG elements
    svg_container = d3.select("body").append("svg");
    rect = svg_container.append("svg:rect");
    x_axis_s = d3.svg.axis();
    y_axis_s = d3.svg.axis();
    x_axis_g = svg_container.append("g");
    y_axis_g = svg_container.append("g");
    x_axis_l = svg_container.append("text");
    y_axis_l = svg_container.append("text");
    
    // Checkbox EVENT
    $("#display_labels_checkbox").click(function(){
        LOG("Display labels checkbox has changed...");
        
        if (this.checked) {
            display_labels=true;
        } else {
            LOG("remove labels...");
            display_labels=false;
        }
        draw_graph();
    });

    //Draw
    update_scales();
    draw_background();
    draw_graph();

    //If there is no job, then do this for testing
    //test_dummy_data();

    var cur_job = $.parseJSON(dexen.get_cur_job());
    cur_job_changed(cur_job);

    var inds = $.parseJSON(dexen.get_all_inds());
    for (var i = 0; i < inds.length; i++) {
        on_inds_updated(inds[i].job_info, inds[i].inds);
    }
});

